package cloudeventstest

import (
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"errors"
	"fmt"
	"log"
	"net/url"
	"os"
	"sort"
	"time"
)

type Signer struct {
	privateKey *rsa.PrivateKey
	publicKey  *rsa.PublicKey
}

func (s *Signer) SetPublicKey(path string) (err error) {
	var (
		data []byte
		tmp  any
	)
	if data, err = os.ReadFile(path); err != nil {
		return
	}

	p, _ := pem.Decode(data)
	if p == nil {
		return errors.New("failed to decode PEM")
	}
	if tmp, err = x509.ParsePKIXPublicKey(p.Bytes); err != nil {
		return
	}

	s.publicKey = tmp.(*rsa.PublicKey)
	return
}

func (s *Signer) SetPrivateKey(path string) (err error) {
	var (
		data []byte
	)
	if data, err = os.ReadFile(path); err != nil {
		return
	}

	p, _ := pem.Decode(data)

	s.privateKey, err = x509.ParsePKCS1PrivateKey(p.Bytes)
	return
}

func (s *Signer) SignatureMap(data map[string]interface{}) map[string]string {
	signatureMap := s.MakeSignatureMap(false)
	delete(signatureMap, "signature")
	signatureMap["signature"] = s.Sign(s.signMsg(data, signatureMap))

	return signatureMap
}

func (s *Signer) MakeSignatureMap(empty bool) map[string]string {
	signatureMap := map[string]string{
		"signatureMethod":  "Rsa",
		"signatureVersion": "1",
		"timeStamp":        time.Now().In(time.UTC).Format("2006-01-02T15:04:05Z"),
		"signature":        "",
	}

	if empty {
		for key := range signatureMap {
			signatureMap[key] = ""
		}
	}

	return signatureMap
}

func (s *Signer) Sign(signMsg string) (signature string) {
	var (
		authCipher []byte
		err        error
	)

	if authCipher, err = rsa.EncryptPKCS1v15(rand.Reader, s.publicKey, []byte(signMsg)); err != nil {
		log.Printf("Signer Sign faild: %s\n", err)
		return
	}
	authB64 := base64.StdEncoding.EncodeToString(authCipher)

	signature = url.QueryEscape(authB64)

	//log.Infof("signature: %s", signature)
	return
}

func (s *Signer) signMsg(data map[string]interface{}, signatureMap map[string]string) string {
	var (
		keys   []string
		newMap = make(map[string]string)
	)

	for k, v := range data {
		newMap[k] = fmt.Sprintf("%v", v)
		keys = append(keys, k)
	}

	for k, v := range signatureMap {
		newMap[k] = v
		keys = append(keys, k)
	}
	sort.Strings(keys)
	params := url.Values{}
	for _, key := range keys {
		params.Add(key, newMap[key])
	}

	return params.Encode()
}

func (s *Signer) Valid(data map[string]interface{}, signatureMap map[string]string) (ok bool, err error) {
	var (
		sTime      time.Time
		authB64    string
		authCipher []byte
		signMsg    []byte
	)

	if sTime, err = time.ParseInLocation("2006-01-02T15:04:05Z", signatureMap["timeStamp"], time.UTC); err != nil {
		return
	}

	//过期了
	if sTime.Add(5 * time.Minute).Before(time.Now().UTC()) {
		return
	}

	signature := signatureMap["signature"]
	delete(signatureMap, "signature")

	if authB64, err = url.QueryUnescape(signature); err != nil {
		return
	}

	if authCipher, err = base64.StdEncoding.DecodeString(authB64); err != nil {
		return
	}

	if signMsg, err = rsa.DecryptPKCS1v15(rand.Reader, s.privateKey, authCipher); err != nil {
		return
	}

	ok = s.signMsg(data, signatureMap) == string(signMsg)
	return
}
